#include "instructions/references/athrow.h"


#include "rtda/heap/class.h"
#include "rtda/heap/string_pool.h"
#include "native/java/lang/Throwable.hpp"

#include <iostream>

namespace jvm {
    namespace instructions {

        void ATHROW::execute(std::shared_ptr<rtda::Frame> pFrame) {
            auto pEx = pFrame->getOperandStack()->popRef();
            if (pEx == nullptr) {
                LOG_ERROR("java.lang.NullPointerException");
                throw std::runtime_error("java.lang.NullPointerException");
            }

            auto pThread = pFrame->getThread();
            if (!pThread)
            {
                LOG_ERROR("null pointer!");
                throw std::runtime_error("null pointer!");
            }

            if (!findAndGotoExceptionHandler(pThread, pEx))
            {
                handleUncaughtException(pThread, pEx);
            }
        }

        bool findAndGotoExceptionHandler(std::shared_ptr<rtda::Thread> pThread,
            heap::ObjectPtr pEx) {

            while (true) {
                auto pFrame = pThread->currentFrame();
                int pc = pFrame->getNextPC() - 1;
                auto pclass = pEx->getClass().lock();

                int handlerPC = pFrame->getMethod()->findExceptionHandler(pclass, pc);
                if (handlerPC > 0) {
                    auto pStack = pFrame->getOperandStack();
                    pStack->clear();
                    pStack->pushRef(pEx);
                    pFrame->setNextPC(handlerPC);
                    return true;
                }

                pThread->popFrame();
                if (pThread->isStackEmpty()) {
                    break;
                }
            }
            return false;
        }

        void handleUncaughtException(std::shared_ptr<rtda::Thread> pThread,
            heap::ObjectPtr pEx)
        {
            pThread->clearStack();
            auto pclass = pEx->getClass().lock();
            if (!pclass)
            {
                LOG_ERROR("null pointer!");
                throw std::runtime_error("null pointer!");
            }

            auto pJMsg = pEx->getRefVar("detailMessage", "Ljava/lang/String;");
            if (!pJMsg)
            {
                LOG_ERROR("null pointer!");
                throw std::runtime_error("null pointer!");
            }
            std::string Msg = heap::StringPool::getInstance().CPPString(pJMsg);
            std::cout << pclass->getJavaName() << ": " << Msg << std::endl;

            auto jvm_stack_msg = pEx->getExtra();
            try
            {
                auto pStes = std::any_cast<std::vector<std::shared_ptr<lang::StackTraceElement>>>(jvm_stack_msg);
                for (auto& pSte : pStes) {
                    std::cout << "\tat " << pSte->toString() << std::endl;
                }
            }
            catch (const std::bad_any_cast& e)
            {
                LOG_ERROR("handleUncaughtException Error: get wrong jvm_stack_msg");
                throw;
            }

        }

    } // namespace instructions
} // namespace jvm